import sys, socket, struct, exceptions, re
import pcap

protocols = {socket.IPPROTO_UDP:'udp',
             socket.IPPROTO_TCP:'tcp',
             socket.IPPROTO_IPV6:'ipv6',
             socket.IPPROTO_ICMP:'icmp'}


class packetParser():
    
    def __init__(self):
        self.packet = {}
        self.dataPointer = 0
        self.level = 10

        self.httpGetPattern = re.compile('GET.+HTTP/1[.][0-9]')
        self.httpHostPattern = re.compile('Host:[. a-zA-Z0-9]+')

    def setLevel(self, level):
        self.level = level

    def packetParse(self, packet):

        #Initialize needed variables
        self.dataPointer = 0
        self.packet.clear()
        self.packet['raw'] = packet
        self.packet.update({'eth':False,'ip':False,'udp':False,'tcp':False,'arp':False,'icmp':False})

        try:
            #Goes through packet and tries to parse defined things
            if self.level==0:
                self.dataParse(packet)
                return self.packet

            if self.ethParse(packet):
                self.packet['eth']=True
            if self.level==1:
                self.dataParse(packet[self.dataPointer:])
                return self.packet

            #0x0800 = 2048, means ipv4
            #0x86DD = 34525,means ipv6
            if self.packet['eth_type']==2048 or self.packet['eth_type']==34525:
            
                if self.ipParse(packet[self.dataPointer:]):
                    self.packet['ip']=True
                if self.level==2:
                    self.dataParse(packet[self.dataPointer:])
                    return self.packet

                if cmp(protocols[self.packet['ip_protocol']],"tcp")==0:
                    if self.tcpParse(packet[self.dataPointer:]):
                        self.packet['tcp']=True
                    if self.level==3:
                        self.dataParse(packet[self.dataPointer:])
                        
                    try:
                        #self.httpParse(packet[self.dataPointer:])
                        self.dataParse(packet[self.dataPointer:])
                    except ParseError:
                        self.dataParse(packet[self.dataPointer:])

                elif cmp(protocols[self.packet['ip_protocol']],"udp")==0:
                    if self.udpParse(packet[self.dataPointer:]):
                        self.packet['udp']=True
                    if self.level==3:
                        self.dataParse(packet[self.dataPointer:])
                        return self.packet
                    self.dataParse(packet[self.dataPointer:])

                elif cmp(protocols[self.packet['ip_protocol']],"icmp")==0:
                    self.packet['icmp']=True
                    self.packet['payload']=""
                
                #Means that the protocol is not supported
                else:
                    return -1

            #0x0806 = 2054, means arp
            elif self.packet['eth_type']==2054:
                self.packet['arp']=True
                self.packet['payload']=""

            #Erronous ethernet header value
            else:
                return -1
            
        except ParseError as e:
            #Erronous packet
            #print e.msg
            return -1
        except DataError:
            #Empty payload
            self.packet['payload'] = ""

        return self.packet
    

    #Data link layer protocols

    def ethParse(self, p):

        pk = {}
        if (len(p)<14): raise ParseError("Invalid packet.")

        #Preamble 7 bytes + start frame delimiter 1 byte
        #(0x55 x 7) + 0xD5 == (85 x 7) + 213
        
        pk['eth_mac_destination']=p[0:6]
        pk['eth_mac_source']=p[6:12]
        hdrType = socket.ntohs(struct.unpack('H',p[12:14])[0])

        if hdrType > 1536:
            pk['llc_header'] = False
            pk['eth_type'] = hdrType
            self.dataPointer += 14
        else:
            pk['llc_header'] = True
            pk['eth_len'] = hdrType
            
            pk['llc_dsap'] = ord(p[14])
            pk['llc_ssap'] = ord(p[15])
            
            #These three are only for SNAP
            if (pk['llc_dsap'] == 0xaa) and (pk['llc_ssap'] == 0xaa):
                pk['llc_ctrl'] = p[16]
                pk['llc_org'] = p[17:20]
                pk['eth_type'] = socket.ntohs(struct.unpack('H',p[20:22])[0])
                self.dataPointer += 22
            else:
                #self.dataPointer += 17
                '''SNAP is needed for the ethertype option.'''
                raise ParseError("LLC without SNAP is not supported.")

        self.packet.update(pk)
        #self.dataPointer+=14
        
        return True


    #Internet layer protocols
    '''
    Checks the IP-version-field and parses all the information
    according to the packet's version number.
    '''
    def ipParse(self, p):

        pk = {}
        pk['ip_version']=(ord(p[0]) & 0xf0) >> 4

        #IPv4-packet
        if pk['ip_version']==4:
            if (len(p)<20): raise ParseError("Invalid IPv4-section.")
            #Number of 32-bit words in IP-header
            pk['ip_header_len']=ord(p[0]) & 0x0f
            #Type of service field
            pk['ip_tos']=(ord(p[1]) & 0xfc) >> 2
            #Explicit congestion notification
            pk['ip_ecn']=(ord(p[1]) & 0x03) 
            #Entire datagram size, including header and data
            pk['ip_len']=socket.ntohs(struct.unpack('H',p[2:4])[0])
            #Identification field
            pk['ip_id']=socket.ntohs(struct.unpack('H',p[4:6])[0])
            #3-bit field for controlling or identifying fragments
            pk['ip_flags']=(ord(p[6]) & 0xe0) >> 5
            #Offset of a fragment relative to the beginning of the unfragmented datagram
            pk['ip_fragment_offset']=(ord(p[6])&0x1f)+ord(p[7])
            #Time to live prevents datagrams from persisting
            pk['ip_ttl']=ord(p[8])
            #Protocol used in the data portion of the datagram
            pk['ip_protocol']=ord(p[9])
            #Header checksum for error-checking
            pk['ip_checksum']=p[10:12]
            #IPv4 sender and receiver addresses
            pk['ip_source_address']=pcap.ntoa(struct.unpack('i',p[12:16])[0])
            pk['ip_destination_address']=pcap.ntoa(struct.unpack('i',p[16:20])[0])
            if pk['ip_header_len']>5:
                #Additional header fields
                pk['ip_options']=p[20:(4*pk['ip_header_len'])]

            self.dataPointer+=(4*pk['ip_header_len'])

        #IPv6-packet
        elif pk['ip_version']==6:
            #raise ParseError("Nope")
            if (len(p)<40): raise ParseError("Invalid IPv6-section.")
            #Holds 2 values: DSCP and ECN
            pk['ip_traffic_class']=(ord(p[0])+ord(p[1])&0x0ff0)>>4#(ord(p[0:2]) & 0x0ff0)>>4
            #For giving real-time applications special service
            pk['ip_flow_label']=(ord(p[1])&0x0f)+ord(p[2])+ord(p[3])
            #Size of payload in octets, including extension headers
            pk['ip_len']=p[4:6]
            #Specifies the type of the next header
            pk['ip_protocol']=ord(p[6])
            #Limit of hops (replaces ttl field in IPv4)
            pk['ip_hop_limit']=p[7]
            #IPv6 sender and receiver addresses
            pk['ip_source_address']=socket.inet_ntop(socket.AF_INET6, p[8:24])
            pk['ip_destination_address']=socket.inet_ntop(socket.AF_INET6, p[24:40])

            self.dataPointer+=40

        else:
            raise ParseError("IP-packet not recognized.")

        self.packet.update(pk)

        return True


    #Transport layer protocols

    def udpParse(self, p):

        pk = {}
        
        if (len(p)<8): raise ParseError("Invalid UDP-section.")
        pk['udp_source_port']=socket.ntohs(struct.unpack('H',p[0:2])[0])
        pk['udp_destination_port']=socket.ntohs(struct.unpack('H',p[2:4])[0])
        pk['udp_length']=socket.ntohs(struct.unpack('H',p[4:6])[0])
        pk['udp_checksum']=p[6:8]

        self.packet.update(pk)
        self.dataPointer+=8
        
        return True                                        


    def tcpParse(self, p):

        pk = {}
        
        if (len(p)<20): raise ParseError("Invalid TCP-section.")
        pk['tcp_source_port']=socket.ntohs(struct.unpack('H',p[0:2])[0])
        pk['tcp_destination_port']=socket.ntohs(struct.unpack('H',p[2:4])[0])
        #Accumulated number of the first data byte for this packet in this session
        pk['tcp_seqnum']=socket.ntohl(struct.unpack('I',p[4:8])[0])
        #This value is the next expected sequence number
        pk['tcp_acknum']=socket.ntohl(struct.unpack('I',p[8:12])[0])
        #Size of the TCP header in 32-bit words (min 5, max 15)
        pk['tcp_data_offset']=(ord(p[12]) & 0xf0) >> 4
        #For future use and should be set to zero
        pk['tcp_reserved']=(ord(p[12]) & 0x0e) >> 1
        #9 1-bit control flags
        pk['tcp_flags']=(ord(p[12])&0x01)+ord(p[13])
        #The size of the receive window
        pk['tcp_window_size']=socket.ntohs(struct.unpack('H',p[14:16])[0])
        #Checksum for error-checking of the header and data
        pk['tcp_checksum']=p[16:18]
        #Offset from the sequence number indicating the last urgent byte
        pk['tcp_urgent_pointer']=socket.ntohs(struct.unpack('H',p[18:20])[0])
        if (pk['tcp_data_offset']>5):
            #Additional TCP options
            pk['tcp_options']=p[20:(4*pk['tcp_data_offset'])]
            
        self.packet.update(pk)
        self.dataPointer+=(pk['tcp_data_offset']*4)

        return True
    

    '''Application layer protocols'''

    def httpParse(self, p):
        pk = {}
        if(len(p)<=0): raise ParseError("Invalid HTTP-section.")
        elif self.packet['tcp_source_port']==80 or self.packet['tcp_destination_port']==80:
            get = self.httpGetPattern.match(p)
            host = self.httpHostPattern.match(p)
            if get and host:
                host = host.group(0)
                print "HTTP-GET requested, {0}".format(host)
        

    def dataParse(self, p, dataLen=None):

        pk = {}
        if (len(p)<=0): raise DataError("Packet contains no payload.")
        if dataLen and (dataLen > 0 and dataLen < len(p)):
            pk['payload'] = p[:dataLen]
        else:
            pk['payload'] = p
            
        self.packet.update(pk)



class ParseError(Exception):
    def __init__(self, msg):
        self.msg = msg

class DataError(Exception):
    def __init__(self, msg):
        self.msg = msg


def handlePacket(pktlen, data, timestamp):
    print "Length:",pktlen,"Time:",timestamp
    paaket = t.packetParse(data)
    print paaket

if __name__=="__main__":
    pobj = pcap.pcapObject()
    pobj.open_live('eth0', 1600, False, 100)
    t = packetParser()
    while True:
        pobj.dispatch(1, handlePacket)

    #fl = open('ipv6_test','r')
    #fl.read(24)
    #fl.read(16)
    #data = fl.read()

    #pp = packetParser()

    #print pp.packetParse(data)
